package com.kancy.vip.util;

import javax.tools.*;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * java源文件动态编译
 * @author kancy
 * @date 2019/3/17 13:55
 */
public class JavaUtil {
	private static final Map<String,Class<?>> classCache = new ConcurrentHashMap();
	private static final Pattern packagePattern = Pattern.compile("package\\s+([0-9A-Za-z_.]+)\\s*;");
	private static final Pattern classPattern = Pattern.compile("public\\s+class\\s+([\\w\\$]+)\\s*");
	private static volatile JavaCompiler javaCompiler;

	private JavaUtil() {}

	private static JavaCompiler getJavaCompiler() {
		if (javaCompiler == null) {
			synchronized (JavaUtil.class) {
				if (javaCompiler == null) {
					javaCompiler = ToolProvider.getSystemJavaCompiler();
				}
			}
		}
		return javaCompiler;
	}

	/**
	 * 编译java文件
	 * @param sourceFileInputPath
	 * @return
	 */
	public static boolean compileJavaFile(String sourceFileInputPath) {
		return compileJavaFile(sourceFileInputPath, getDefaultOutputPath());
	}

	/**
	 * 动态编译
	 * @param sourceFileInputPath  java文件
	 * @param classFileOutputPath  class文件路径
	 * @return
	 */
	public static boolean compileJavaFile(
			String sourceFileInputPath, String classFileOutputPath) {
		// 1.获取编译器
		JavaCompiler javaCompiler = getJavaCompiler();
		// 2.获取文件管理器
		StandardJavaFileManager fileManager = getStandardJavaFileManager(javaCompiler);
		// 3.获取java源文件对象
		Iterable<? extends JavaFileObject> compilationUnits = fileManager
				.getJavaFileObjectsFromFiles(Arrays.asList(new File(sourceFileInputPath)));
		// 4.指定编译参数，指定class文件存放路径
		Iterable<String> options = Arrays.asList("-d", classFileOutputPath);
		// 5.创建编译任务，并且返回结果
		return javaCompiler.getTask(null, fileManager, null, options,
				null, compilationUnits).call();
	}



	/**
	 * 编译并且加载类
	 * @param javaCodes
	 * @return
	 */
	public static Class<?> compileAndLoadClass(String javaCodes) {
		return compileAndLoadClass(parseClassName(javaCodes), javaCodes, true);
	}

	public static Class<?> compileAndLoadClass(String javaCodes, boolean delTempClass) {
		return compileAndLoadClass(parseClassName(javaCodes), javaCodes, delTempClass);
	}

	/**
	 * 编译并且加载类
	 * @param className className
	 * @param javaCodes javaCodes
	 * @return Class
	 */
	public static Class<?> compileAndLoadClass(String className, String javaCodes, boolean delTempClass) {
		// 0.从缓存中获取
		Class<?> clazz = getClassFromCache(className, javaCodes);
		if(clazz != null) return clazz;

		// 1.获取编译器
		JavaCompiler compiler = getJavaCompiler();
		// 2.获取标准文件管理器
		StandardJavaFileManager fileManager = getStandardJavaFileManager(compiler);
		// 3.指定java源文件
		StrSrcJavaObject srcObject = new StrSrcJavaObject(className, javaCodes);
		Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(srcObject);
		// 4.设置动态编译参数
		String outputClassPath = getDefaultOutputPath();
		Iterable<String> options = Arrays.asList("-d", outputClassPath);
		// 5.创建编译任务
		JavaCompiler.CompilationTask task =
				compiler.getTask(null, fileManager, null,
						options, null, fileObjects);
		// 6.等待编译完成，并且返回结果
		if (task.call()) {
			clazz = loadClass(className);
			if(clazz != null){
				// 加入缓存
				putClassToCache(className, javaCodes, clazz);
				// 删除临时class
				if(delTempClass){
					String classFilePath = outputClassPath + srcObject.getName();
					classFilePath = classFilePath.replaceAll("\\+","/")
							.replaceAll("/+","/")
							.replace(".java",".class");
					File file = new File(classFilePath);
					if(file.exists() && file.isFile()){
						file.delete();
					}
				}
			}
		}
		return clazz;
	}


	/**
	 * 从指定路径进行加载
	 * @param classFilePath
	 * @param className
	 * @return
	 */
	public static Class<?> loadClass(String classFilePath, String className){
		Class<?> clazz = null;
		try {
			URL classPathUrl = new File(classFilePath).toURI().toURL();
			URL[] urls = new URL[] {classPathUrl};
			URLClassLoader loader = new URLClassLoader(urls);
			clazz = loader.loadClass(className);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return clazz;
	}


	/**
	 * 从类路径加载
	 * @param className
	 * @return
	 */
	public static Class<?> loadClass(String className){
		try {
			return Class.forName(className);
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 加入缓存
	 * @param className
	 * @param javaCodes
	 * @param clazz
	 */
	private static void putClassToCache(String className, String javaCodes, Class<?> clazz) {
		String cacheKey = className+"@"+javaCodes;
		if(!classCache.containsKey(cacheKey)){
			classCache.put(cacheKey,clazz);
		}
	}

	/**
	 * 从缓存中获取
	 * @param className
	 * @param javaCodes
	 * @return
	 */
	private static Class<?> getClassFromCache(String className, String javaCodes) {
		String cacheKey = className+"@"+javaCodes;
		if(classCache.containsKey(cacheKey)){
			return classCache.get(cacheKey);
		}else{
			return null;
		}
	}

	/**
	 * 获取默认标准文件管理器
	 * @param compiler
	 * @return
	 */
	private static StandardJavaFileManager getStandardJavaFileManager(JavaCompiler compiler) {
		return compiler.getStandardFileManager(null, null, null);
	}

	private static class StrSrcJavaObject extends SimpleJavaFileObject {
		private String content;
		public StrSrcJavaObject(String name, String content) {
			super(URI.create("string:///" + name.replace('.', '/')
					+ Kind.SOURCE.extension), Kind.SOURCE);
			this.content = content;
		}
		public CharSequence getCharContent(boolean ignoreEncodingErrors) {
			return content;
		}
	}

	/**
	 * 获取默认的输出路径
	 * @return
	 */
	private static String getDefaultOutputPath() {
		String outDir = null;
		try {
			File classPath = new File(Thread.currentThread().getContextClassLoader()
					.getResource("").toURI());
			outDir = classPath.getAbsolutePath() + File.separator;
		} catch (URISyntaxException e1) {
			e1.printStackTrace();
		}
		return outDir;
	}

	/**
	 * 从java源代码中获取class名称
	 * @param javaCode
	 * @return
	 */
	private static String parseClassName(String javaCode){
		Matcher packageMatcher = packagePattern.matcher(javaCode);
		String className = null;
		if(packageMatcher.find()){
			className = packageMatcher.group(1);
		}

		Matcher classMatcher = classPattern.matcher(javaCode);
		if(classMatcher.find()){
			if(className == null){
				className = classMatcher.group(1);
			}else{
				className += "." + classMatcher.group(1);
			}
		}
		return className;

	}

	public static void main(String[] args) throws Exception {
		StringBuffer sb = new StringBuffer();
		sb.append("package test.demo;");
		sb.append("import java.util.*;");
		sb.append("public class Test1  {");
		sb.append("  public static void main(String[] args){");
		sb.append("    System.out.println(Arrays.toString(args));");
		sb.append("  }");
		sb.append("}");
		Class<?> class1 = compileAndLoadClass("test.demo.Test1", sb.toString(), false);
		System.out.println(class1);
		Class<?> class2 = compileAndLoadClass(sb.toString());
		System.out.println(class2);

		// 运行
		Method main = class1.getDeclaredMethod("main", String[].class);
		main.invoke(class1, (Object) new String[]{"hello","world"});

	}

}
